﻿#region License

// Created by John Batte on 03/22/2012 at 7:35 AM.
// Last code cleanup: 03/22/2012 at 8:02 AM.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#endregion

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace BatteCode.Fx.RegularExpressions
{
	/// <summary>
	/// 	Defines a regular expression that is always compiled, and that can be created via implicit string conversion.
	/// </summary>
	public class CompiledRegex : Regex
	{
		/// <summary>
		/// 	Initializes a new instance of the <see cref="CompiledRegex" /> class.
		/// </summary>
		/// <param name="pattern"> The pattern. </param>
		/// <param name="options"> The options. </param>
		public CompiledRegex(string pattern, RegexOptions options = RegexOptions.None)
			: base(pattern, GetOptions(options))
		{
			GroupDetails = GetGroupNumbers().ToDictionary(i => i, GroupNameFromNumber);
		}

		/// <summary>
		/// 	Gets the group details.
		/// </summary>
		/// <value> The group details. </value>
		public IDictionary<int, string> GroupDetails{ get; private set; }

		/// <summary>
		/// 	Builds a new <see cref="CompiledRegex" /> instance from the specified raw text. Reserved characters are automatically escaped, causing them to serve as required components of the resulting pattern. Whitespace is always interpreted to be of variable length (i.e. all contiguous whitespace blocks are replaced with the \s+ search term). Useful in scenarios where existing text is used to match against other values.
		/// </summary>
		/// <param name="text"> The raw text. </param>
		/// <param name="options"> The options. </param>
		/// <returns> A new <see cref="CompiledRegex" /> instance. </returns>
		public static CompiledRegex BuildFrom(string text, RegexOptions options = RegexOptions.None)
		{
			const string whiteSpace = @"\s+";
			string escapedText = string.Join(whiteSpace, Split(text, whiteSpace).Select(Escape).ToArray());
			return new CompiledRegex(escapedText, options);
		}

		///<summary>
		///	Gets the first pattern match as a key-value dictionary. Useful with patterns that use named groups.
		///</summary>
		///<param name="input"> The input. </param>
		///<param name="removeSystemGroups"> True to remove system-added groups (such as group "0", "1", etc.) before extracting dictionary values. The default is true. </param>
		///<returns> The dictionary of matches. </returns>
		public IDictionary<string, string> DictionaryMatch(string input, bool removeSystemGroups = true)
		{
			Match match = Match(input);
			return ConvertMatchToDictionary(match, removeSystemGroups);
		}

		///<summary>
		///	Gets the pattern matches as key-value dictionaries. Useful with patterns that use named groups.
		///</summary>
		///<param name="input"> The input. </param>
		///<param name="removeSystemGroups"> True to remove system-added groups (such as group "0") before extracting dictionary values. The default is true. </param>
		///<returns> A collection of matches formatted as key-value dictionaries. </returns>
		public IEnumerable<IDictionary<string, string>> DictionaryMatches(string input, bool removeSystemGroups = true)
		{
			MatchCollection matches = Matches(input);
			return matches.Cast<Match>().Select(m => ConvertMatchToDictionary(m, removeSystemGroups));
		}

		/// <summary>
		/// 	Performs an implicit conversion from <see cref="System.String" /> to <see cref="CompiledRegex" /> .
		/// </summary>
		/// <param name="pattern"> The pattern. </param>
		/// <returns> The result of the conversion. </returns>
		public static implicit operator CompiledRegex(string pattern)
		{
			return new CompiledRegex(pattern);
		}

		private static RegexOptions GetOptions(RegexOptions originalOptions)
		{
			return (originalOptions & RegexOptions.Compiled) != RegexOptions.Compiled
			       	? originalOptions | RegexOptions.Compiled
			       	: originalOptions;
		}

		private static bool IsSystemGroup(string groupName)
		{
			int value;
			return int.TryParse(groupName, out value);
		}

		private IDictionary<string, string> ConvertMatchToDictionary(Match match, bool removeSystemGroups)
		{
			Dictionary<string, string> allValues = GroupDetails.Values
				.ToDictionary(s => s, s => match.Groups[s].Value);

			return removeSystemGroups
			       	? allValues.SkipWhile(pair => IsSystemGroup(pair.Key)).ToDictionary(p => p.Key, p => p.Value)
			       	: allValues;
		}
	}
}